﻿using SYMetro.Domain.Modal.Membership;
using SYMetro.Domain.Repository.Membership;
using SYMetro.ServiceContract.Membership;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YiDa.Domain.Repositories;
using YiDa.Infrastructure.Caching;
using SYMetro.DataObjects;
using AutoMapper;
using YiDa.Domain.Specifications;
using System.ServiceModel;
using YiDa.Domain;
using System.Security.Cryptography;
using System.Web.Configuration;
using System.Configuration.Provider;
using YiDa.Infrastructure;
using System.Globalization;
using System.Text.RegularExpressions;


namespace SYMetro.Application.Membership
{
    public class UserService : ApplicationService, IUserService
    {
        IUserRepository _userRepository = null;
        public UserService(IRepositoryContext context,
            IUserRepository userRepository)
            : base(context)
        {
            _userRepository = userRepository;
        }

        #region IUserService Members

        public UserDataObjectList CreateUser(MembershipConfigDataObject config, UserDataObjectList users)
        {
            UserDataObjectList result = new UserDataObjectList();

            foreach (var u in users)
            {
                string password = u.Password;
                if (!SecUtility.ValidateParameter(ref password, true, true, false, 128))
                {
                    throw new DomainException("用户:{0}密码无效", u.UserName);
                }
                string salt = GenerateSalt();
                string pass = EncodePassword(password, config.PasswordFormatValue, salt);
                if (pass.Length > 128)
                {
                    throw new DomainException("用户:{0}密码长度超出系统范围", u.UserName);
                }
                string encodedPasswordAnswer;
                if (!string.IsNullOrEmpty(u.PasswordAnswer))
                {
                    encodedPasswordAnswer = EncodePassword(u.PasswordAnswer.ToLower(CultureInfo.InvariantCulture), config.PasswordFormatValue, salt);
                }
                else
                {
                    encodedPasswordAnswer = u.PasswordAnswer;
                }
                if (!SecUtility.ValidateParameter(ref encodedPasswordAnswer, config.RequiresQuestionAndAnswer, true, false, 128))
                {
                    throw new DomainException("用户:{0}必需提供密码答案", u.UserName);
                }
                string username = u.UserName;
                if (!SecUtility.ValidateParameter(ref username, true, true, true, 256))
                {
                    throw new DomainException("用户:{0}用户名无效", u.UserName);
                }
                string email = u.Email;
                if (!SecUtility.ValidateParameter(ref email,
                                   config.RequiresUniqueEmail,
                                   config.RequiresUniqueEmail,
                                   false,
                                   256))
                {
                    throw new DomainException("用户:{0}无效的电子邮件", u.UserName);
                }

                string passwordQuestion = null;
                if (!SecUtility.ValidateParameter(ref passwordQuestion, config.RequiresQuestionAndAnswer, true, false, 256))
                {
                    throw new DomainException("用户:{0}无效的密码问题", u.UserName);
                }

                if (password.Length < config.MinRequiredPasswordLength)
                {
                    throw new DomainException("用户:{0}密码长度不能少于{1}位", u.UserName, config.MinRequiredPasswordLength);
                }

                int count = 0;

                for (int i = 0; i < password.Length; i++)
                {
                    if (!char.IsLetterOrDigit(password, i))
                    {
                        count++;
                    }
                }

                if (count < config.MinRequiredNonalphanumericCharacters)
                {
                    throw new DomainException("用户:{0}非字母数字符号不能少于{1}位", u.UserName, config.MinRequiredNonalphanumericCharacters);
                }

                if (config.PasswordStrengthRegularExpression.Length > 0)
                {
                    if (!Regex.IsMatch(password, config.PasswordStrengthRegularExpression))
                    {
                        throw new DomainException("用户:{0}密码不符合正则表达式的要求", u.UserName);
                    }
                }               
                var du = Mapper.Map<UserDataObject, User>(u);
                if (du.ID == Guid.Empty)
                {
                    du.ID = Guid.NewGuid();
                }
                du.CreateDate = DateTime.UtcNow;
                du.PasswordSalt = salt;
                du.PasswordFormat = config.PasswordFormatValue;
                du.UserName = username;
                du.LoweredUserName = username.ToLower();
                du.Password = pass;
                du.Email = email;
                du.LoweredEmail = email.ToLower();
                du.PasswordQuestion = passwordQuestion;
                du.PasswordAnswer = encodedPasswordAnswer;
                _userRepository.Add(du);

                result.Add(Mapper.Map<User,UserDataObject>(du));
            }

            Context.Commit();

            return result;
        }

        public bool DeleteUser(string username)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user != null)
            {
                _userRepository.Remove(user);
            }

            return true;
        }

        public bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user == null)
            {
                return false;
            }
            string encodedPasswd = EncodePassword(password, user.PasswordFormat, user.PasswordSalt);
            if (encodedPasswd != user.Password)
            {
                throw new DomainException("密码错误");
            }
            string encodedPasswordAnswer;
            if (!string.IsNullOrEmpty(newPasswordAnswer))
            {
                encodedPasswordAnswer = EncodePassword(newPasswordAnswer.ToLower(CultureInfo.InvariantCulture), user.PasswordFormat, user.PasswordSalt);
            }
            else
            {
                encodedPasswordAnswer = newPasswordAnswer;
            }
            user.PasswordAnswer = encodedPasswordAnswer;
            user.PasswordQuestion = newPasswordQuestion;
            _userRepository.Update(user);
            Context.Commit();
            return true;
        }

        public string GetPassword(string username, string passwordAnswer)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user == null)
            {
                return null;
            }
            string encodedAnswer = EncodePassword(passwordAnswer.ToLower(CultureInfo.InvariantCulture), user.PasswordFormat, user.PasswordSalt);
            if (encodedAnswer != user.PasswordAnswer)
            {
                throw new DomainException("问题答案不一致");
            }
            return UnEncodePassword(user.Password, user.PasswordFormat);
        }

        public bool ChangePassword(MembershipConfigDataObject config, string username, string oldPassword, string newPassword)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user == null)
            {
                return false;
            }
            string encodedPasswd = EncodePassword(oldPassword, user.PasswordFormat, user.PasswordSalt);
            if (user.Password == encodedPasswd)
            {
                if (newPassword.Length < config.MinRequiredPasswordLength)
                {
                    throw new DomainException("密码长度不能少于{0}位", config.MinRequiredPasswordLength);
                }
                user.Password = EncodePassword(newPassword, user.PasswordFormat, user.PasswordSalt);
                user.LastPasswordChangedDate = DateTime.UtcNow;
                _userRepository.Update(user);
                Context.Commit();
                return true;
            }
            return false;
        }

        public string ResetPassword(MembershipConfigDataObject config, string username, string passwordAnswer)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user == null)
            {
                return null;
            }
            string encodedAnswer = EncodePassword(passwordAnswer.ToLower(CultureInfo.InvariantCulture), user.PasswordFormat, user.PasswordSalt);
            if (encodedAnswer != user.PasswordAnswer)
            {
                throw new DomainException("问题答案不一致");
            }
            var newPassword = GeneratePassword(config);
            user.Password = EncodePassword(newPassword, user.PasswordFormat, user.PasswordSalt);
            _userRepository.Update(user);
            Context.Commit();
            return newPassword;
        }

        public bool UpdateUser(MembershipConfigDataObject config, UserDataObjectList users)
        {
            foreach (var dto in users)
            {
                var u = _userRepository.GetByKey(Guid.Parse(dto.ID));
                Mapper.Map<UserDataObject, User>(dto, u);
                _userRepository.Update(u);
            }
            Context.Commit();
            return true;
        }

        public bool ValidateUser(string username, string password)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user == null)
            {
                return false;
            }
            string encodedPasswd = EncodePassword(password, user.PasswordFormat, user.PasswordSalt);
            if (user.Password == encodedPasswd)
            {
                if (user.IsApproved)
                {
                    user.LastLoginDate = DateTime.UtcNow;
                    _userRepository.Update(user);
                    Context.Commit();
                    return true;
                }
                else
                {
                    return false;
                }               
            }
            else
            {
                return false;
            }
        }

        public bool UnlockUser(string username)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user == null)
            {
                return false;
            }
            user.IsLockedOut = false;
            user.FailedPasswordAttemptCount = user.FailedPasswordAnswerAttemptCount = 0;
            user.FailedPasswordAttemptWindowStart = user.FailedPasswordAnswerAttemptWindowStart = DateTime.UtcNow;
            user.LastLockoutDate = DateTime.UtcNow;
            _userRepository.Update(user);
            Context.Commit();
            return true;
        }

        public UserDataObject GetUser(Guid Id, bool userIsOnline)
        {
            var u = _userRepository.GetByKey(Id);
            if (u == null)
            {
                return null;
            }
            if (userIsOnline)
            {
                u.LastActivityDate = DateTime.UtcNow;
                _userRepository.Update(u);
                Context.Commit();
            }
            return Mapper.Map<User, UserDataObject>(u);

        }

        public UserDataObject GetUser(string username, bool userIsOnline)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.UserName == username));
            if (user != null && userIsOnline)
            {
                user.LastActivityDate = DateTime.UtcNow;
                _userRepository.Update(user);
                Context.Commit();
            }

            return Mapper.Map<User, UserDataObject>(user);
        }

        public UserDataObject GetUserNameByEmail(string email)
        {
            var user = _userRepository.Find(Specification<User>.Eval(u => u.Email==email));
            return Mapper.Map<User, UserDataObject>(user);
        }

        public UserDataObjectList GetAllUsers()
        {
            UserDataObjectList r = new UserDataObjectList();
            _userRepository.FindAll().ToList().ForEach(u => {
                r.Add(Mapper.Map<User, UserDataObject>(u));
            });
            return r;
        }

        public int GetNumberOfUsersOnline(MembershipConfigDataObject config)
        {
            var time = DateTime.UtcNow.AddMinutes(-1 * System.Web.Security.Membership.UserIsOnlineTimeWindow);
            var count = _userRepository.FindAll(Specification<User>.Eval(u => u.LastActivityDate > time)).Count();
            return count;
        }

        public UserDataObjectList FindUsersByName(string usernameToMatch)
        {
            UserDataObjectList r = new UserDataObjectList();
            _userRepository.FindAll(Specification<User>.Eval(u => u.UserName.Contains(usernameToMatch))).ToList().ForEach(u =>
            {
                r.Add(Mapper.Map<User, UserDataObject>(u));
            });
            return r;
        }

        public UserDataObjectList FindUsersByEmail(string emailToMatch)
        {
            UserDataObjectList r = new UserDataObjectList();
            _userRepository.FindAll(Specification<User>.Eval(u => u.Email.Contains(emailToMatch))).ToList().ForEach(u =>
            {
                r.Add(Mapper.Map<User, UserDataObject>(u));
            });
            return r;
        }

        public string GeneratePassword(MembershipConfigDataObject config)
        {
            return System.Web.Security.Membership.GeneratePassword(
                      config.MinRequiredPasswordLength < 14 ? 14 : config.MinRequiredPasswordLength, config.MinRequiredNonalphanumericCharacters);
        }
        #endregion

        #region Membership Members


        protected byte[] DecryptPassword(byte[] encodedPassword)
        {
            if (Utils.getPerperty<bool>("IsDecryptionKeyAutogenerated", typeof(MachineKeySection)))
            {
                throw new ProviderException(SR.GetString("Can not use encrypted passwords with autogen keys"));
            }
            return (byte[])Utils.InvokeMethod(typeof(MachineKeySection), "EncryptOrDecryptData", new object[] { false, encodedPassword, null, 0, encodedPassword.Length, 0, false, false });
        }

        protected byte[] EncryptPassword(byte[] password)
        {
            if (Utils.getPerperty<bool>("IsDecryptionKeyAutogenerated", typeof(MachineKeySection)))
            {
                throw new ProviderException(SR.GetString("Can not use encrypted passwords with autogen keys"));
            }
            return (byte[])Utils.InvokeMethod(typeof(MachineKeySection), "EncryptOrDecryptData", new object[] { true, password, null, 0, password.Length, 0, false, false });
        }

        internal string UnEncodePassword(string pass, int passwordFormat)
        {
            switch (passwordFormat)
            {
                case 0:
                    return pass;

                case 1:
                    throw new ProviderException(SR.GetString("Can not decode hashed password"));
            }
            byte[] encodedPassword = Convert.FromBase64String(pass);
            byte[] bytes = this.DecryptPassword(encodedPassword);
            if (bytes == null)
            {
                return null;
            }
            return Encoding.Unicode.GetString(bytes, 0x10, bytes.Length - 0x10);
        }

        internal string GenerateSalt()
        {
            byte[] buf = new byte[16];
            (new RNGCryptoServiceProvider()).GetBytes(buf);
            return Convert.ToBase64String(buf);
        }

        internal string EncodePassword(string pass, int passwordFormat, string salt)
        {
            if (passwordFormat == 0) // MembershipPasswordFormat.Clear
                return pass;
            
            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bAll = new byte[bSalt.Length + bIn.Length];
            byte[] bRet = null;

            Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
            Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
            if (passwordFormat == 1)
            { // MembershipPasswordFormat.Hashed
                //HashAlgorithm s = HashAlgorithm.Create(System.Web.Security.Membership.HashAlgorithmType);     //4.0这样的Hash算法得到的结果不一致
                HashAlgorithm s = HashAlgorithm.Create("SHA512");                
                bRet = s.ComputeHash(bAll);
            }
            else
            {
                bRet = EncryptPassword(bAll);
            }

            return Convert.ToBase64String(bRet);
        }

        #endregion
    }
}
